#include "Ground.h"
#include "SharedConstants.h"
#include "Water.h"
#include "Landscape.h"

#include "HeightMapCoordinates.h"
#include "TextureUtils.h"


Ground::Ground(HeightMap i_height_map, double i_cell_size, double i_watel_level)
  : m_cell_size_in_height_map(i_cell_size),
  m_water_level(i_watel_level),
  m_height_map(i_height_map)
  {
  _CreateGround();

  std::vector<EGroundType> tmp_vect;
  for(size_t i = 0; i < m_height_map.GetSizeX() - 1; ++i)
    {
    tmp_vect.reserve(m_height_map.GetSizeY());
    for(size_t j = 0; j < m_height_map.GetSizeY() - 1; ++j)
      {
      tmp_vect.push_back(GT_GRASS);
      }
    m_ground_types.push_back(tmp_vect);
    tmp_vect.clear();
    }
  _UpdateSurface();
  }


void Ground::_CreateGround()
  {
  std::shared_ptr<Model3D> model(new Model3D());
  m_landscape = std::shared_ptr<Landscape>(new Landscape(GroundConstants::ORIGIN_VECTOR, model));
  };

void Ground::_UpdateSurface()
  {
  GenerateSurface(m_landscape->GetModel(), m_height_map, m_landscape->GetPosition().X(),
    m_landscape->GetPosition().Y(),
    m_landscape->GetPosition().Z(), m_cell_size_in_height_map);
  ApplyGroundTypes(m_landscape->GetModel(), m_height_map, m_landscape->GetPosition().X(),
    m_landscape->GetPosition().Y(),
    m_landscape->GetPosition().Z(),m_cell_size_in_height_map, m_ground_types); 
  }

Ground::~Ground()
  {

  }

EGroundType Ground::GetGroundType(const TVector2& i_point) const 
  {
  auto indexes = _FindHeightMapIndexes(i_point);
  return m_ground_types[indexes.first][indexes.second];
  }

EGroundType Ground::GetGroundType(size_t i_hmap_coord_x, size_t i_hmap_coord_y) const 
  {
  return m_ground_types[i_hmap_coord_x][i_hmap_coord_y];
  }

void Ground::SetGroundType(size_t i_x, size_t i_y, EGroundType i_type)
  {
  m_ground_types[i_x][i_y] = i_type;
  }

void Ground::ResetGroundTypes(const TGroundTypeMap &i_map)
  {
  m_ground_types = i_map;
  _UpdateSurface();
  }


std::pair<size_t, size_t> Ground::_FindHeightMapIndexes(const TVector2& i_point) const
  {
  return HeightMapUtils::FindHeightMapIndexes(i_point, TVector2(m_landscape->GetPosition().X(),
    m_landscape->GetPosition().Y()), m_cell_size_in_height_map);
  }

void Ground::CreateHighland(TVector2 i_point, double i_height_change)
  {
  auto dimentions = _FindHeightMapIndexes(i_point);
  size_t x_dimention = dimentions.first;
  size_t y_dimention = dimentions.second;
  HeightMapUtils::CreateHill(m_height_map, x_dimention, y_dimention, i_height_change, D_UP);
  _UpdateSurface();
  }

void Ground::CreateHole(TVector2 i_point, double i_height_change)
  {
  auto dimentions = _FindHeightMapIndexes(i_point);
  size_t x_dimention = dimentions.first;
  size_t y_dimention = dimentions.second;

  HeightMapUtils::CreateHill(m_height_map, x_dimention, y_dimention, i_height_change, D_DOWN);
  _UpdateSurface();
  }

void Ground::AddWaterModel(std::shared_ptr<Water> ip_water)
  {
  // Apply Water Textures
  ip_water->GetModel().SetMaterialName("Water/WaterSurface");
  Textures::ApplyTextures(ip_water->GetModel()); 
  m_water_models.push_back(ip_water);
  }


const Landscape& Ground::GetLandscape() const
  {
  return *m_landscape;
  }

std::vector<ICustomModelRenderable*> Ground::GetObjectsToRender() const
  {
  std::vector<ICustomModelRenderable*> result;
  for(auto water : m_water_models)
    {
    result.push_back(water.get());
    }

  result.push_back(m_landscape.get());
  return result;
  }

const std::vector<std::shared_ptr<Water>>& Ground::GetWaterModels() const
  {
  return m_water_models;	
  };

void Ground::CreateWalkway(TVector2 i_point)
  {
  auto dimentions = _FindHeightMapIndexes(i_point);

  m_ground_types[dimentions.first][dimentions.second] = GT_WALKWAY;

  ApplyGroundTypes(m_landscape->GetModel(), m_height_map,
    m_landscape->GetPosition().X(), m_landscape->GetPosition().Y(),
    m_landscape->GetPosition().Z(),m_cell_size_in_height_map, m_ground_types); 
  }

void Ground::SetGrass(TVector2 i_point)
  {
  auto dimentions = _FindHeightMapIndexes(i_point);

  m_ground_types[dimentions.first][dimentions.second] = GT_GRASS;

  ApplyGroundTypes(m_landscape->GetModel(), m_height_map, m_landscape->GetPosition().X(),
    m_landscape->GetPosition().Y(),
    m_landscape->GetPosition().Z(),m_cell_size_in_height_map, m_ground_types); 

  }

namespace
  {
  enum ELocations
    {
    T_TOP_TRIANGLE,
    T_BOTTOM_TRIANGLE,
    T_VERTEX
    };
  }

double Ground::GetHeightForXY(TVector2 i_point) const
  {
  auto indexes = _FindHeightMapIndexes(i_point);

  // true if no interpolation required: exact point
  bool exact_x = false;
  bool exact_y = false;
  double x_diff = i_point.X() - m_cell_size_in_height_map*indexes.first  ;
  double y_diff = i_point.Y() - m_cell_size_in_height_map*indexes.second ;
  if (x_diff == 0.0)
    exact_x = true;
  if (y_diff == 0.0)
    exact_y = true;

  ELocations location;
  if (x_diff == 0.0 && y_diff == 0.0)
    {
    location = ELocations::T_VERTEX;
    }
  else if (y_diff > m_cell_size_in_height_map - x_diff)
    location = ELocations::T_BOTTOM_TRIANGLE;
  else //if (x_diff < y_diff)
    location = ELocations::T_TOP_TRIANGLE;

  // if point is on vertex - return value from height map
  if (location == ELocations::T_VERTEX)
    {
    // height from height map in cell
    double height = m_height_map.Get(indexes.first, indexes.second);
    return height;
    }

  // determine triangle
  TVector3 pt1, pt2, pt3;
  switch (location)
    {
    case T_BOTTOM_TRIANGLE:
      pt1 = TVector3((indexes.first + 1) * GroundConstants::CELL_SIZE, (indexes.second + 1)*GroundConstants::CELL_SIZE, m_height_map.Get(indexes.first + 1, indexes.second + 1));
      pt2 = TVector3((indexes.first + 1) * GroundConstants::CELL_SIZE, indexes.second * GroundConstants::CELL_SIZE, m_height_map.Get(indexes.first + 1, indexes.second));
      pt3 = TVector3(indexes.first * GroundConstants::CELL_SIZE, (indexes.second + 1) * GroundConstants::CELL_SIZE, m_height_map.Get(indexes.first, indexes.second + 1));
      break;
    case T_TOP_TRIANGLE:
      pt1 = TVector3(indexes.first * GroundConstants::CELL_SIZE, indexes.second*GroundConstants::CELL_SIZE, m_height_map.Get(indexes.first, indexes.second));
      pt2 = TVector3((indexes.first + 1) * GroundConstants::CELL_SIZE, indexes.second * GroundConstants::CELL_SIZE, m_height_map.Get(indexes.first + 1, indexes.second));
      pt3 = TVector3(indexes.first * GroundConstants::CELL_SIZE, (indexes.second + 1) * GroundConstants::CELL_SIZE, m_height_map.Get(indexes.first, indexes.second + 1));
      break;
    }

  // find z-coordinate for i_point using barycentric coordinates

  double x = i_point.X();
  double y = i_point.Y();

  // solve equation for barycentric coordinates using Kramer's algorithm
  double delta   = (pt1.X() - pt3.X())*(pt2.Y() - pt3.Y()) - (pt2.X() - pt3.X())*(pt1.Y() - pt3.Y());
  double delta_1 = - (y - pt3.Y()) * (pt2.X() - pt3.X()) + (x - pt3.X()) * (pt2.Y() - pt3.Y());
  double delta_2 = (y - pt3.Y()) * (pt1.X() - pt3.X()) - (x - pt3.X()) * (pt1.Y() - pt3.Y());

  // barycentric coordinates
  double l1 = delta_1 / delta;
  double l2 = delta_2 / delta;
  double l3 = 1 - l1 - l2;

  // if point is not in triangle
  if (l1 < 0 || l2 < 0 || l3 < 0)
    {
    // this should not happen
    }

  double z = pt1.Z() * l1 + pt2.Z() * l2 + pt3.Z() * l3;
  return z;
  }

std::pair<size_t, size_t> Ground::GetCellPosition(const TVector2& i_point) const
  {
  return _FindHeightMapIndexes(i_point);
  }

HeightMap& Ground::GetHeightMap()
  {
  return m_height_map;
  }

const HeightMap & Ground::GetHeightMap() const
  {
  return m_height_map;
  }

const TGroundTypeMap& Ground::GetGroundTypeMap() const
  {
  return m_ground_types;
  }

void Ground::RemoveWaterModel(size_t i_model_index)
  {
  m_water_models.erase(std::remove(m_water_models.begin(), m_water_models.end(),
    *(m_water_models.begin() + i_model_index)), m_water_models.end());
  }

